package com.xwltz.web.advice;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.xwltz.core.utils.ObjectUtils;
import com.xwltz.core.utils.StringUtils;
import com.xwltz.core.utils.XmlUtils;
import org.springframework.core.MethodParameter;
import org.springframework.core.ResolvableType;
import org.springframework.lang.NonNull;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.servlet.ModelAndView;

import javax.annotation.Nullable;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.lang.reflect.Array;
import java.lang.reflect.Modifier;
import java.util.*;

@SuppressWarnings({"unchecked"})
public class RequestResolver implements HandlerMethodArgumentResolver {

	@Override
	public boolean supportsParameter(@Nullable MethodParameter methodParameter) {
		Optional<RequestAttributes> requestAttributes = Optional.ofNullable(RequestContextHolder.getRequestAttributes());
		return requestAttributes.isPresent();
	}

	@Override
	public Object resolveArgument(@NonNull MethodParameter parameter, ModelAndViewContainer mavContainer, @Nullable NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {

		Optional<RequestAttributes> requestAttributes = Optional.ofNullable(RequestContextHolder.getRequestAttributes());
		if (requestAttributes.isEmpty()) return null;

		JSONObject jsonObject;
		HttpServletRequest request = ((ServletRequestAttributes) requestAttributes.get()).getRequest();

		if (request.getParameter(parameter.getParameterName()) != null) {
			jsonObject = new JSONObject();
			jsonObject.put(parameter.getParameterName(), request.getParameter(parameter.getParameterName()));
		} else if (request.getContentType() != null && request.getContentType().contains("json")) {
			if (request.getAttribute("body") != null)
				jsonObject = (JSONObject) request.getAttribute("body");
			else {
				String jsonString = StringUtils.read(new BufferedReader(new InputStreamReader(request.getInputStream())));

				if (jsonString.trim().startsWith("[")) {
					jsonObject = new JSONObject();
					jsonObject.put(parameter.getParameterName(), JSONArray.parseArray(jsonString));
				} else
					jsonObject = JSONObject.parseObject(jsonString);
				if (jsonObject == null) {
					request.setAttribute("body", new JSONObject());
					return null;
				}

				jsonObject.put("_body", jsonString);
				request.setAttribute("body", jsonObject);
			}
		} else if (request.getContentType() != null && request.getContentType().contains("xml")) {
			if (request.getAttribute("body") != null)
				jsonObject = (JSONObject) request.getAttribute("body");
			else {
				String xml = StringUtils.read(new BufferedReader(new InputStreamReader(request.getInputStream())));

				jsonObject = JSONObject.parseObject(JSONObject.toJSONString(XmlUtils.decodeSimpleXml(xml)));

				if (jsonObject == null) {
					request.setAttribute("body", new JSONObject());
					return null;
				}

				jsonObject.put("_body", xml);
				request.setAttribute("body", jsonObject);
			}
		} else if (request.getContentType() != null && request.getContentType().contains("text")) {
			if (request.getAttribute("body") != null)
				jsonObject = (JSONObject) request.getAttribute("body");
			else {
				jsonObject = new JSONObject();
				jsonObject.put("_body", StringUtils.read(new BufferedReader(new InputStreamReader(request.getInputStream()))));

				request.setAttribute("body", jsonObject);
			}
		} else {
			if (request.getAttribute("body") != null)
				jsonObject = (JSONObject) request.getAttribute("body");
			else {
				jsonObject = new JSONObject();
				request.getParameterMap().forEach((s, strings) -> {
					if (strings.length > 0)
						jsonObject.put(s, strings[0]);
				});

				jsonObject.put("_body", StringUtils.read(new BufferedReader(new InputStreamReader(request.getInputStream()))));
				request.setAttribute("body", jsonObject);
			}
		}

		Class<?> targetClass = parameter.getNestedParameterType();

		if (Collection.class.isAssignableFrom(targetClass)) {
			ResolvableType type = ResolvableType.forMethodParameter(parameter);

			Collection<Object> collection;

			if (Modifier.isInterface(targetClass.getModifiers()) || Modifier.isAbstract(targetClass.getModifiers())) {
				if (List.class.isAssignableFrom(targetClass)) {
					collection = new ArrayList<>();
				} else if (Set.class.isAssignableFrom(targetClass)) {
					collection = new HashSet<>();
				} else {
					return null;
				}
			} else {
				collection = (Collection<Object>) ObjectUtils.getInstance(targetClass);
			}

			JSONArray jsonArray = jsonObject.getJSONArray(parameter.getParameterName());

			if (jsonArray != null) {
				for (int i = 0; i < jsonArray.size(); i++) {
					if (type.getGeneric(0).resolve() == null)
						collection.add(jsonArray.get(i));
					else {
						Object o = jsonArray.getObject(i, type.getGeneric(0).resolve());
						collection.add(o);
					}
				}
			}

			return collection;
		} else if (targetClass.isArray()) {
			JSONArray jsonArray = jsonObject.getJSONArray(parameter.getParameterName());
			Object array = Array.newInstance(targetClass.getComponentType(), jsonArray.size());

			for (int i = 0; i < jsonArray.size(); i++) {
				Array.set(array, i, jsonArray.getObject(i, targetClass.getComponentType()));
			}
			return array;
		}
//		else if (targetClass.isEnum()) {
//			Object value = jsonObject.getOrDefault(parameter.getParameterName(), null);
//			return CommonTools.getEnumElement(value, targetClass);
//		}
		else if (!ModelAndView.class.isAssignableFrom(targetClass)) {
			if (jsonObject.containsKey(parameter.getParameterName())) {
				return jsonObject.getObject(parameter.getParameterName(), targetClass);
			}

			if (Boolean.class.isAssignableFrom(targetClass) || Number.class.isAssignableFrom(targetClass) || CharSequence.class.isAssignableFrom(targetClass)) {
				return jsonObject.getObject(parameter.getParameterName(), targetClass);
			}

			Object data = jsonObject.toJavaObject(targetClass);

			if (data != null && targetClass.isAssignableFrom(data.getClass()) && !String.class.isAssignableFrom(data.getClass())) {
				return data;
			}

			return jsonObject.getObject(parameter.getParameterName(), targetClass);
		}

		return new ModelAndView();
	}
}
